concrete5でAuth0を使って認証する方法を考えてみる
はじめに
タイトルの通りですが、Auth0をconcrete5で使うには、どのような方法があるのか気になっておりまして、試してみました。
concrete5の認証機能について
下記のようなログイン画面(デザインのカスタマイズが可能)があり、ユーザーIDとパスワードによる認証が可能です。 concrete5のユーザーとして、concrete5の管理者やユーザーの他に、concrete5で構築したサイト訪問者(サイトのユーザー)も登録することで、会員サイトを構築することが可能になります。
その他にfacebook, twitter, googleといったSNSアカウントでの認証も可能です。 各SNSでの認証を有効にする設定を行うと、このようにログイン画面にメニューが表示されるようになります。
以前にtwitterでの認証機能を試したことがあるため、参考までに記事のリンクを貼っておきます。
Auth0を使って認証する対象について考える
concrete5の標準の認証機能を使用する場合、concrete5のデータベース上に認証情報(ユーザーID、パスワードなど)を保持し、そこにユーザーとしてconcrete5の管理者やサイト制作者、サイト訪問者(サイトのユーザー)などを登録していきます。
Auth0を使って認証を行うにあたって、下記のような検討ができるのではないかと思いました。
- 全てのユーザー(concrete5の管理者やサイト制作者、サイト訪問者など)を対象とするのか?
- サイト訪問者だけを対象とするのか?
全てのユーザー(concrete5の管理者やサイト制作者、サイト訪問者など)を対象とする場合
簡単に言うと標準のログイン画面、機能を一切使わず、Auth0に置き換えることになります。
イメージとしては、concrete\single_pages\login.php
やconcrete\controlles\single_page\login.php
といった直接関係する部分や、関連する部分をオーバーライドしていくことになると思います。
サイト訪問者だけを対象とする場合
サイトは複数存在しており、メンテナンスをそれぞれ別の担当者(業者)が行なっているけど、各サイトへの認証処理は共通のIDで行いたい。そのためにAuth0のような統合認証基盤を導入するといった場合です。
この場合は、concrete5の管理者やサイト制作者の認証はconcrete5の標準機能を使い、サイト訪問者の認証はAuth0を使うことになるため、機能を追加するイメージです。
今回はこちらの実現方法について試してみました。
Auth0で認証する場合、concrete5のユーザーアカウントは不要か?
認証することだけを考えると不要なのですが、ログインセッションの管理や、ユーザーアカウントが存在していることを前提とした機能を使用することを考えると、内部的にはユーザーアカウントを作成して保持しておくのが良さそうであると判断しました。
そのため、下記の対応を実施しております。
- concrete5上に、個々のAuth0ユーザーとひもづくアカウントを作成する
- Auth0の認証が成功したら、concrete5上のアカウントも認証状態にさせておく
Auth0ユーザーとconcrete5のユーザーをどうやって紐づけるのか?
今回の検証では事前にconcrete5上にユーザーを作っておきましたが、実際に運用する場合は下記が対応案になると思っております。
- 方法1)Auth0でのサインアップ処理をトリガーにして、concrete5上のユーザーを作成する。concrete5側にユーザー作成のためのAPIが必要になるためAPIをカスタマイズ開発する必要がある。ユーザー名(uName)はAuth0の
client_id(subクレームの値)
と同じにしておきたいが、|
がconcrete5のバリデーションチェックに引っかかってしまう。
カスタマイズ可能ならば、|
を許容するようにして、無理な場合はAuth0のclient_id
の頭からauth0|
を除去した値で登録する。パスワードはダミーのものを設定しておく。
Auth0でのサインアップ処理をトリガーにする方法はこちらのブログ記事が参考になります。
- 方法2)Auth0で認証されたユーザーの
client_id(subクレームの値)
の値でconcrete5のユーザー検索を行い、存在しない場合はユーザーを作成する
concrete5のログインセッションの管理をどうするか?
今回はデフォルトのままですが、Auth0のセッション有効期間 < concrete5のセッション有効期間にしておく必要がございます。
試した内容
Auth0の認証画面への遷移方法をどうするか?
標準機能の拡張として、カスタム認証タイプ(Custom Authentication Type)にAuth0を追加する方法があると思います。
Advanced: Creating a Custom Authentication Type
下記はgoogleですが、Auth0でログイン
といったボタンを増やすということです。
各SNSアカウントでの認証だと、googleでログイン
とかtwitterでログイン
とか、各ボタンを並べて選択してもらうのだと思いますが、Auth0の場合はユーザーはそれを特に意識しないため、ログイン画面をそのまま表示させるのが良いだろうと思いました。
そのため、標準の認証URL(https://{domain}/concrete5/(index.php)/login
)とは別に、https://{domain}/concrete5/(index.php)/customer/auth0_login
といったURLをサイト訪問者用に別で用意することにしました。
※今回は私のローカル環境で試したため、http通信にしているのと、URLからindex.php
を消すrewrite処理は入れておりません
Auth0アプリケーションの作成
Auth0の管理画面のApplications
メニューを選択して、画面上のCREATE APPLICATION
ボタンを押して作成します。
Allowed Callback URLs
とAllowed Logout URLs
の設定も忘れないようにしてください。
Auth0 PHP SDKのインストール
concrete5アプリケーションのルートディレクトリへ移動し、下記を実行。
composer require auth0/auth0-php:"~5.0"
下記の警告が出ましたが、とりあえずはそのままにして今回の動作確認を行いました。
Warning: Ambiguous class resolution, "Normalizer" was found in both "/{ローカル環境のインストールパス}/concrete5-8.5.1/concrete/vendor/patchwork/utf8/src/Normalizer.php" and "/{ローカル環境のインストールパス}/concrete5-8.5.1/concrete/vendor/voku/portable-utf8/src/Normalizer.php", the first will be used. Skipped installation of bin bin/doctrine-dbal for package doctrine/dbal: file not found in package Skipped installation of bin bin/doctrine-migrations for package doctrine/migrations: file not found in package Skipped installation of bin bin/doctrine for package doctrine/orm: file not found in package Skipped installation of bin bin/doctrine.php for package doctrine/orm: file not found in package Skipped installation of bin bin/export-plural-rules for package gettext/languages: file not found in package Skipped installation of bin bin/export-plural-rules.php for package gettext/languages: file not found in package
シングルページの作成
下記を追加しました。
application\single_pages\customer\auth0_login.php
application\controllers\single_page\customer\auth0_login.php
application\single_pages\customer\auth0_login.php
の内容
動作確認用に表示したい内容を記載しました。
<h2>Auth0での認証結果</h2> <?php echo "name=" . $userInfo['name'] . "<br />"; echo "sub=" . $userInfo['sub'] . "<br />"; echo "nickname=" . $userInfo['nickname'] . "<br />"; echo "picture=" . $userInfo['picture'] . "<br />"; echo "updated_at=" . $userInfo['updated_at'] . "<br />"; echo "email=" . $userInfo['email'] . "<br />"; echo "email_verified=" . $userInfo['email_verified'] . "<br />"; ?> <hr /> <h2>concrete5のユーザー情報</h2> <?php echo "uID=" . $c5userInfo->getUserID() . "<br />"; echo "uName=" . $c5userInfo->getUserName() . "<br />"; echo "uEmail=" . $c5userInfo->getUserEmail() . "<br />"; ?> <hr /> <?php if(!$userInfo): ?> 未ログイン状態 <?php else: ?> <a href="http://localhost:8888/concrete5/index.php/customer/auth0_login/logout">Auth0_Logout</a> <?php endif ?>
`application\controllers\single_page\customer\auth0_login.phpの内容
Auth0のサイトにあるPHPでのクイックスタートを参考にしました。
<?php namespace Application\Controller\SinglePage\Customer; use Concrete\Core\Page\Controller\PageController; use Concrete\Core\User\User; use Core; use Auth0\SDK\Auth0; class Auth0Login extends PageController { private $auth0; // Concrete5のPage Controllerの初期化処理でAuth0の設定を入れておく public function on_start(){ // Auth0の設定 $this->auth0 = new Auth0([ 'domain' => '{Auth0のドメイン}', 'client_id' => '{Auth0のクライアントID}', 'client_secret' => '{Auth0のクライアントシークレット}', 'redirect_uri' => 'http://localhost:8888/concrete5/index.php/customer/auth0_login/callback', 'scope' => 'openid profile email', 'persist_id_token' => true, 'persist_access_token' => true, 'persist_refresh_token' => true, ]); } // ログイン時に呼ばれるメソッド public function view(){ $this->auth0->login(); } // Auth0からのコールバックを受けるメソッド public function callback(){ $userInfo = $this->auth0->getUser(); if (!$userInfo) { // 認証できていない場合の処理は、今回はスキップ } else { /*** concrete5ユーザーをログイン状態にさせる ***/ // concrete5上のユーザーは、ユーザー名をAuth0のclient_idの頭の「auth0|」を取った値で登録しているため、subクレームの値を分割 // (concrete5のバリデーションチェックにより、'|'をユーザー名に含めることができなかったため) $auth0sub[] = explode('auth0|', $userInfo['sub']); // ユーザー名からconcrete5のUserInfoオブジェクトを取得する $c5userInfo = Core::make('Concrete\Core\User\UserInfoRepository')->getByName($auth0sub[0]); // UserInfoオブジェクトからユーザーIDを取得して、concrete5上のユーザーをログイン状態にする User::loginByUserID($c5userInfo->getUserID()); // Viewで表示できるようにUserInfoの値をセットしておく $this->set('userInfo', $userInfo); $this->set('c5userInfo', $c5userInfo); } } // Auth0のログアウト処理を行うメソッド public function logout(){ /*** concrete5上のユーザーをログアウトさせる ***/ // ログイン中のconcrete5ユーザーを取得 $u = new User(); // ログイン中のconcrete5ユーザーをログアウト $u->logout(); /*** Auth0からのログアウト ***/ $this->auth0->logout(); $return_to = 'http://' . $_SERVER['HTTP_HOST'] . '/concrete5/index.php/'; $logout_url = sprintf('http://%s/v2/logout?client_id=%s&returnTo=%s', '{Auth0のドメイン}', '{Auth0のクライアントID}', $return_to); header('Location: ' . $logout_url); die(); } }
concrete5の管理画面上でシングルページを追加、設定
ページとテーマ
> シングルページ
を開き、作成したシングルページを追加します。
/customer/auth0_login
の名前
をAuth0 Login
に変更しているのは、サイトマップ
メニュー画面上で変更しております。
concrete5の管理画面上で外部リンクの追加
サイトマップ
を開き、作成したシングルページのメニュー(Login
)にログアウト処理を実行するためのリンクを追加します。
ログインユーザーのみに表示されるように設定します。
動作確認
まずはAuth0に未ログイン状態です。Login
メニューにAuth0_Login
のみが表示されておりますので、concrete5上にもログインしていないことがわかります。Auth0にログインするためにAuth0_Login
をクリックします。
Auth0の認証画面に遷移しました。ユーザーIDとパスワードを入力してログインします。 (ユーザーアカウントを作成していない場合は、サインアップをします)
Auth0での認証が完了し、コールバックを受けました。 Auth0で認証したユーザーのUserInfo情報を取得できております。画面上に`Auth0_Logout'リンクが表示されているため、Auth0へログイン状態であることが確認できます。
Login
メニューにAuth0_Logout
も表示されましたので、concrete5へのログインもできております。
ログアウトをすると、Auth0とconcrete5からログアウトされました。
おわりに
今回は、とりあえず試してみたという結果でした。
Laravel上でAuth0を使用する方法を参考にしたりして、concrete5で最適な実現方法を探していきたいと思います。 あとは他のCMSでも試してみたいと思います。